using System;
using System.Linq;
using System.Collections.Generic;

namespace Linquid
{
	/// <summary>
	/// Linquid related class extensions.
	/// </summary>
	public static class Extensions
	{
		/// <summary>
		/// Binds all UI actions together and joins the output of
		/// resulting sources
		/// </summary>
		/// <param name="uis">UI actions to bind</param>
		/// <returns>UI action that produces a source of all values
		/// in 'uis'</returns>
		public static UI<Source<A>> Concat<A>(this IEnumerable<UI<Source<A>>> uis)
		{
			return uis.Aggregate((ui1, ui2) =>
				      			 from s1 in ui1
				          		 from s2 in ui2
				          		 select s1.Join(s2));
		}
		
		/// <summary>
		/// Collects collection of sources into source of array
		/// </summary>
		/// <param name="sources">Sources to collect</param>
		/// <returns>Source emitting array for each emit of
		/// any source in 'sources'</returns>
		public static Source<A[]> Collect<A>(this IEnumerable<Source<A>> sources)
		{
			return Source<int>.Collect(sources.ToArray());
		}
		
		/// <summary>
		/// Lifts function of one argument into domain of sources
		/// </summary>		
		/// <returns>Function of sources</returns>
		public static Func<Source<A>, Source<B>> Lift<A, B>(this Func<A, B> f)
		{
			return s => s.Map(f);
		}
		
		/// <summary>
		/// Lifts function of one argument into domain of sources and
		/// immediately applies the result to given value. 
		/// </summary>		
		/// <returns>Result of application.</returns>
		public static Source<B> Lift<A, B>(this Func<A, B> f, Source<A> source)
		{
			return source.Map(f);
		}
		
		/// <summary>
		/// Lifts function of two arguments into domain of sources
		/// </summary>		
		/// <returns>Function of sources</returns>
		public static Func<Source<A>, Source<B>, Source<C>> Lift<A, B, C>(
			this Func<A, B, C> f)
		{
			return (s1, s2) => s1.Map2(s2, f);
		}
		
		
		/// <summary>
		/// Lifts function of two arguments into domain of sources and
		/// immediately applies the result to the given values. 
		/// </summary>		
		/// <returns>Result of application.</returns>
		public static Source<C> Lift<A, B, C>(this Func<A, B, C> f, 
		                                      Source<A> sourceA, 
		                                      Source<B> sourceB)
		{
			return sourceA.Map2(sourceB, f);
		}
	}
}

